package service

import (
	"context"
	"encoding/json"
	"fmt"
	"time"

	"wxpay/internal/dao"
	"wxpay/internal/dto"
	"wxpay/internal/model"
	"wxpay/pkg/merrors"
	"wxpay/pkg/util"

	mchApi "wxpay/pkg/wechat_api"

	"gorm.io/gorm"
)

type withdrawService struct {
	d dao.DataStore
}

func NewWithdrawService(d dao.DataStore) *withdrawService {
	return &withdrawService{d: d}
}

// 提交提现申请
func (s *withdrawService) WithdrawApply(ctx context.Context, withdraw *dto.SubMchWithDrawDTO) (wd *dto.SubMchWithDrawDTO, err error) {
	const op merrors.Op = "withdrawService.WithdrawApply"
	repo := s.d.Withdraw()
	m, err := repo.QuerySubMchWithDrawByBizNo(ctx, s.d.GetTx(ctx), withdraw.BizWithdrawID)
	if val, ok := err.(model.NotFound); ok && val.NotExist() {
		//不存在，创建提现记录
		m = dto.AdaptSubMchWithdraw(withdraw)
		if err := s.d.GetTx(ctx).Transaction(func(tx *gorm.DB) error {
			if e := repo.SaveSubMchWithDraw(ctx, tx, m); e != nil {
				return e
			}
			// 创建提现任务-等待定时任务捞取后执行
			if e := s.createWithdrawTask(ctx, m, tx); e != nil {
				return e
			}
			return nil
		}); err != nil {
			return nil, err
		}
	} else if err != nil {
		return nil, merrors.E(op, merrors.Invalid, err)
	}
	wd = dto.ConvertSubMchWithdraw(m)
	return wd, nil

}

func (s *withdrawService) createWithdrawTask(ctx context.Context, m *model.SubMchWithdraw, tx *gorm.DB) (err error) {
	payload := model.NewSubMchWithdrawTask(m)
	ps, _ := json.Marshal(&payload)
	msg := model.NewMessage(model.TopicWithdrawAction, ps, 0)
	if e := s.d.EventMessage().SaveMessageInTx(ctx, tx, msg); e != nil {
		return e
	}
	return nil
}

func convertWithdrawReq(withdraw *model.SubMchWithdraw) mchApi.SubMchWithdrawRequest {
	return mchApi.SubMchWithdrawRequest{
		SubMchID:     withdraw.SubMchID,
		OutRequestNo: withdraw.OutRequestNo,
		Amount:       withdraw.Amount,
		Remark:       util.ConvertNullString(withdraw.Remark),
		BankMemo:     util.ConvertNullString(withdraw.BankMemo),
	}
}

// 执行提现任务
func (s *withdrawService) ExecuteTask(ctx context.Context, task *model.SubMchWithdrawTask) (err error) {
	const op merrors.Op = "withdrawService.ExecuteTask"
	repo := s.d.Withdraw()
	m, err := repo.QuerySubMchWithDrawByOutNo(ctx, s.d.GetTx(ctx), task.OutRequestNo)
	if val, ok := err.(model.NotFound); ok && val.NotExist() {
		// 不存在直接跳过
		return nil
	}
	if err != nil {
		return
	}
	// 调用提现接口并更新微信提现单号
	if err = s.d.GetTx(ctx).Transaction(func(tx *gorm.DB) error {
		withdraw, e := s.d.Lock().LockSubMchWithdraw(ctx, tx, m.ID)
		if e != nil {
			return e
		}
		if withdraw.Status != model.WithdrawStatusAwait {
			return merrors.E(op, merrors.StatusInvalid, merrors.ErrStatusInValid, fmt.Sprintf("提现单状态:%s, 期望%s", m.Status, model.WithdrawStatusAwait))
		}
		client, e := NewMchService(s.d).BuildMchApiClient(ctx, m.MchID)
		if e != nil {
			return merrors.E(op, merrors.Invalid, e)
		}
		resp, e := client.SubMchWithdraw(ctx, convertWithdrawReq(m))
		if e != nil {
			return merrors.E(op, merrors.Invalid, merrors.ErrWxApi, e)
		}
		withdraw.UpdateWxWithdrawID(resp.WithdrawID)
		if e := repo.SaveSubMchWithDraw(ctx, tx, withdraw); e != nil {
			return e
		}
		// 创建提现查询任务-等待定时任务捞取后执行
		if e := s.createWithdrawQueryTask(ctx, tx, withdraw); e != nil {
			return e
		}
		return nil
	}); err != nil {
		return
	}
	return
}

// 创建提现查询任务
func (s *withdrawService) createWithdrawQueryTask(ctx context.Context, tx *gorm.DB, withdraw *model.SubMchWithdraw) error {
	payload := model.NewSubMchWithdrawQueryTask(withdraw)
	ps, _ := json.Marshal(&payload)
	// 延后15min执行
	msg := model.NewMessage(model.TopicWithdrawQuery, ps, 15*time.Minute)
	if e := s.d.EventMessage().SaveMessageInTx(ctx, tx, msg); e != nil {
		return e
	}
	return nil
}

// 执行提现查询任务
func (s *withdrawService) ExecuteQueryTask(ctx context.Context, task *model.SubMchWithdrawQueryTask) (err error) {
	const op merrors.Op = "withdrawService.ExecuteQueryTask"
	repo := s.d.Withdraw()
	m, err := repo.QuerySubMchWithDrawByOutNo(ctx, s.d.GetTx(ctx), task.OutRequestNo)
	if val, ok := err.(model.NotFound); ok && val.NotExist() {
		// 不存在直接跳过
		return nil
	}
	if err != nil {
		return
	}
	// 调用提现查询接口并更新状态
	if err = s.d.GetTx(ctx).Transaction(func(tx *gorm.DB) error {
		withdraw, e := s.d.Lock().LockSubMchWithdraw(ctx, tx, m.ID)
		if e != nil {
			return e
		}
		if withdraw.IsFinalState() {
			return merrors.E(op, merrors.StatusInvalid, merrors.ErrStatusInValid, fmt.Sprintf("提现单状态已经是终态:%s", m.Status))
		}
		client, e := NewMchService(s.d).BuildMchApiClient(ctx, m.MchID)
		if e != nil {
			return merrors.E(op, merrors.Invalid, e)
		}
		req := mchApi.SubMchWithdrawQueryByOutRequestNoRequest{SubMchID: withdraw.SubMchID, OutRequestNo: withdraw.OutRequestNo}
		resp, e := client.SubMchWithdrawQueryByOutRequestNo(ctx, req)
		if e != nil {
			return merrors.E(op, merrors.Invalid, merrors.ErrWxApi, e)
		}
		if withdraw.UpdateStatus(resp.Status, resp.Reason) {
			if e := repo.SaveSubMchWithDraw(ctx, tx, withdraw); e != nil {
				return e
			}
			// 发送提现更新消息
			payload := model.NewSubMchWithdrawNotification(withdraw)
			ps, _ := json.Marshal(&payload)
			msg := model.NewMessage(model.TopicWithdrawNotify, ps, 0)
			if e := s.d.EventMessage().SaveMessageInTx(ctx, tx, msg); e != nil {
				return e
			}
		}

		switch withdraw.Status {
		case model.WithdrawStatusCreated:
			// 受理成功状态需要继续查询
			// 创建提现查询任务-等待定时任务捞取后执行
			payload := model.NewSubMchWithdrawQueryTask(withdraw)
			ps, _ := json.Marshal(&payload)
			// 延后15min执行
			msg := model.NewMessage(model.TopicWithdrawQuery, ps, 15*time.Minute)
			if e := s.d.EventMessage().SaveMessageInTx(ctx, tx, msg); e != nil {
				return e
			}
		}

		return nil
	}); err != nil {
		return
	}
	return
}

// 根据业务单号查询转账单详情
func (s *withdrawService) QueryByBizNo(ctx context.Context, bizNo string) (withdraw *dto.SubMchWithDrawDTO, err error) {
	const op merrors.Op = "withdrawService.ExecuteQueryTask"
	repo := s.d.Withdraw()
	m, err := repo.QuerySubMchWithDrawByBizNo(ctx, s.d.GetTx(ctx), bizNo)
	if val, ok := err.(model.NotFound); ok && val.NotExist() {
		// 不存在报错
		return nil, merrors.E(op, merrors.NotExist, merrors.ErrNotFound, "转账单不存在")
	}
	if err != nil {
		return
	}
	withdraw = dto.ConvertSubMchWithdraw(m)
	return
}
